package com.ihomey.linkuphome.ui;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelUuid;
import android.support.v4.app.FragmentTransaction;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.widget.Toast;

import com.afollestad.materialdialogs.MaterialDialog;
import com.csr.mesh.ActuatorModelApi;
import com.csr.mesh.ConfigModelApi;
import com.csr.mesh.DataModelApi;
import com.csr.mesh.GroupModelApi;
import com.csr.mesh.LightModelApi;
import com.csr.mesh.MeshService;
import com.csr.mesh.PowerModelApi;
import com.csr.mesh.SensorModelApi;
import com.csr.mesh.SwitchModelApi;
import com.devspark.appmsg.AppMsg;
import com.github.ikidou.fragmentBackHandler.BackHandlerHelper;
import com.ihomey.linkuphome.DeviceController;
import com.ihomey.linkuphome.DeviceStore;
import com.ihomey.linkuphome.R;
import com.ihomey.linkuphome.base.BaseActivity;
import com.ihomey.linkuphome.base.BaseFragment;
import com.ihomey.linkuphome.entities.Device;
import com.ihomey.linkuphome.entities.GroupDevice;
import com.ihomey.linkuphome.entities.LightState;
import com.ihomey.linkuphome.entities.Setting;
import com.ihomey.linkuphome.entities.SingleDevice;
import com.ihomey.linkuphome.listeners.AssociationListener;
import com.ihomey.linkuphome.listeners.BatteryListener;
import com.ihomey.linkuphome.listeners.GroupListener;
import com.ihomey.linkuphome.listeners.RemovedListener;
import com.ihomey.linkuphome.util.HexUtil;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import static com.ihomey.linkuphome.app.Constant.REQUEST_BT_RESULT_CODE;
import static com.ihomey.linkuphome.entities.Device.DEVICE_ADDR_BASE;
import static com.ihomey.linkuphome.entities.Device.DEVICE_ID_UNKNOWN;

/**
 * Created by Administrator on 2017/6/9.
 */

public class MainActivity extends BaseActivity implements DeviceController {

    // Keys used to save settings
    private static final String SETTING_LAST_ID = "lastID";
    private static final int REMOVE_ACK_WAIT_TIME_MS = (10 * 1000);
    private int mSendDeviceId = Device.DEVICE_ID_UNKNOWN;
    private int mGroupAcksWaiting = 0;
    private AssociationListener mAssListener;
    private BatteryListener mBatteryListener;
    private RemovedListener mRemovedListener;
    private GroupListener mGroupAckListener;

    private boolean mConnected = false;
    private boolean mGroupSuccess = true;
    private HashSet<String> mConnectedDevices = new HashSet<String>();

    private int mAssociationTransactionId = -1;

    private MeshService mService = null;
    private HashMap<String, String> address_name = new HashMap<>();
    private SparseIntArray mDeviceIdtoUuidHash = new SparseIntArray();
    private Queue<Integer> mModelsToQueryForGroups = new LinkedList<Integer>();
    private List<Integer> mGroupsToSend;
    private ArrayList<Integer> mNewGroups = new ArrayList<Integer>();

    private DeviceStore mDeviceStore;
    private int mRemovedUuidHash;
    private int mRemovedDeviceId;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDeviceStore = new DeviceStore(this);

        Intent bindIntent = new Intent(this, MeshService.class);
        bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);

        showFragment(HomeFragment.newInstance());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mService.disconnectBridge();
        mService.setHandler(null);
        mMeshHandler.removeCallbacksAndMessages(null);
        unbindService(mServiceConnection);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder rawBinder) {
            mService = ((MeshService.LocalBinder) rawBinder).getService();
            if (mService != null) {
                int lastIdUsed = getSharedPreferences("ihomey", MODE_PRIVATE).getInt(SETTING_LAST_ID, Setting.UKNOWN_ID);
                restoreSettings(lastIdUsed);
                connect();
            }
        }

        public void onServiceDisconnected(ComponentName classname) {
            mService = null;
        }
    };

    private void restoreSettings(int settingsID) {
        // Try to get the settings if we know the ID.
        if (settingsID != Setting.UKNOWN_ID) {
            mDeviceStore.loadSetting(settingsID);
        }
        // save in sharePreferences the last settings used.
        SharedPreferences activityPrefs = getSharedPreferences("ihomey", MODE_PRIVATE);
        SharedPreferences.Editor editor = activityPrefs.edit();

        if (mDeviceStore.getSetting() != null) {
            // save in sharePreferences the last settings used.
            editor.putInt(SETTING_LAST_ID, settingsID);
            editor.commit();

            mDeviceStore.loadAllDevices();
            // set next device id to be used according with the last device used in the database.
            mService.setNextDeviceId(mDeviceStore.getSetting().getLastDeviceIndex() + 1);

        } else {
            // No setting founded. We need to create one...
            Setting setting = new Setting();
            setting.setLastGroupIndex(Device.GROUP_ADDR_BASE + 1);
            mDeviceStore.setSetting(setting, true);
            // save in sharePreferences the last settings used.
            editor.putInt(SETTING_LAST_ID, mDeviceStore.getSetting().getId());
            editor.commit();
        }
        mService.setNetworkPassPhrase("linkuphome");
    }

    private void connect() {
        mService.setHandler(mMeshHandler);
        mService.setLeScanCallback(mScanCallBack);
        mService.setMeshListeningMode(true, true);
        mService.autoConnect(1, 1000, 1000, 0);
    }

    /**
     * Handle messages from mesh service.
     */
    private final Handler mMeshHandler = new MeshHandler(this);

    public Handler getMeshHandler() {
        return mMeshHandler;
    }

    @Override
    public void discoverDevices(boolean enabled, AssociationListener listener) {
        if (enabled) {
            mAssListener = listener;
        } else {
            mAssListener = null;
        }

        //avoiding crashes
        if (mService != null) {
            mService.setDeviceDiscoveryFilterEnabled(enabled);
        }
    }

    @Override
    public void loadSetting(int settingsID) {
        if (settingsID != Setting.UKNOWN_ID) {
            mDeviceStore.loadSetting(settingsID);
        }
    }

    @Override
    public Device getDevice(int deviceId) {
        return mDeviceStore.getDevice(deviceId);
    }

    @Override
    public Device getDevice(int deviceId, int type) {
        return mDeviceStore.getDevice(deviceId, type);
    }

    @Override
    public void removeAllDevices() {
        mDeviceStore.removeAllDevices();
    }

    @Override
    public void cancelTransaction() {
        if (mService != null && mAssociationTransactionId != -1) {
            mService.cancelTransaction(mAssociationTransactionId);
        }
    }

    @Override
    public void removeAllGroupDevices() {
        mDeviceStore.removeAllGroupDevices();
    }

    @Override
    public String getDataBaseAsJson() {
        return mDeviceStore.getDataBaseAsJson();
    }

    @Override
    public boolean setConfigurationFromJson(String configuration, int lightType) {
        return mDeviceStore.setConfigurationFromJson(configuration, lightType);
    }

    @Override
    public LightState getLightState(int deviceId) {
        return mDeviceStore.getLightState(deviceId);
    }

    @Override
    public void getBatteryState(int deviceId, BatteryListener batteryListener) {
        mBatteryListener = batteryListener;
        if (mConnected) {
            if (deviceId < DEVICE_ADDR_BASE && getBoundedDevices(deviceId).isEmpty()) {
                mBatteryListener.onBatteryLevelReceived(Device.DEVICE_ID_UNKNOWN, 0);
                return;
            }
            DataModelApi.sendData(deviceId, HexUtil.decodeHex("B600B6".toCharArray()), false);
        } else {
            mBatteryListener.onBatteryLevelReceived(Device.DEVICE_ID_UNKNOWN, 0);
        }
    }

    @Override
    public List<Device> getGroups() {
        return mDeviceStore.getAllGroups();
    }

    @Override
    public GroupDevice addLightGroup() {
        int nextGroupIndex = mDeviceStore.getSetting().getNextGroupIndexAndIncrement() + 1;
        GroupDevice result = new GroupDevice(nextGroupIndex, getDeviceType(), getString(R.string.group) + " " + (getGroups().size() + 1));
        mDeviceStore.addGroupDevice(nextGroupIndex, getDeviceType(), getString(R.string.group) + " " + (getGroups().size() + 1), true);
        return result;
    }

    @Override
    public void deleteLightGroup(int deviceId) {
        mDeviceStore.removeDevice(deviceId);
    }

    @Override
    public void deleteModel(int groupId, int deviceId) {
        mDeviceStore.removeModel(groupId, deviceId);
    }

    @Override
    public List<SingleDevice> getDevices(int... modelNumber) {
        ArrayList<SingleDevice> result = new ArrayList<SingleDevice>();
        for (Device dev : mDeviceStore.getAllSingleDevices()) {
            if (((SingleDevice) dev).isAnyModelSupported(modelNumber)) {
                result.add((SingleDevice) dev);
            }
        }
        return result;
    }

    @Override
    public List<SingleDevice> getBoundedDevices(int groupId, int... modelNumber) {
        ArrayList<SingleDevice> result = new ArrayList<SingleDevice>();
        for (Device dev : mDeviceStore.getAllSingleDevices()) {
            SingleDevice singleDevice = (SingleDevice) dev;
            if (singleDevice.isAnyModelSupported(modelNumber) && singleDevice.getGroupMembership().contains(groupId)) {
                result.add(singleDevice);
            }
        }
        return result;
    }

    @Override
    public List<SingleDevice> getUnBoundedDevices(int groupId, int... modelNumber) {
        ArrayList<SingleDevice> result = new ArrayList<SingleDevice>();
        for (Device dev : mDeviceStore.getAllSingleDevices()) {
            SingleDevice singleDevice = (SingleDevice) dev;
            if (singleDevice.isAnyModelSupported(modelNumber) && !singleDevice.getGroupMembership().contains(groupId)) {
                result.add(singleDevice);
            }
        }
        return result;
    }

    @Override
    public void setSelectedDeviceId(int deviceId) {
        this.mSendDeviceId = deviceId;
    }

    @Override
    public int getSelectedDeviceId() {
        return mSendDeviceId;
    }

    @Override
    public int getDeviceType() {
        return mDeviceStore.getLightType();
    }

    @Override
    public void setDeviceType(int deviceType) {
        setSelectedDeviceId(Device.DEVICE_ID_UNKNOWN);
        mDeviceStore.setLightType(deviceType);
        mDeviceStore.loadAllDevices();
    }

    @Override
    public void postRunnable(Runnable checkScanInfoRunnable) {
        mMeshHandler.post(checkScanInfoRunnable);
    }

    @Override
    public void removeRunnable(Runnable checkScanInfoRunnable) {
        mMeshHandler.removeCallbacks(checkScanInfoRunnable);
    }

    @Override
    public void updateControls(int selectedDeviceId) {
        setSelectedDeviceId(selectedDeviceId);

    }

    @Override
    public void updateDeviceName(int deviceId, String name) {
        mDeviceStore.updateDeviceName(deviceId, name);
    }

    @Override
    public void updateDeviceLightState(LightState lightState) {
        mDeviceStore.updateDeviceLightState(lightState);
    }

    @Override
    public void associateDevice(int uuidHash, String shortCode) {
        if (shortCode == null) {
            mAssociationTransactionId = mService.associateDevice(uuidHash, 0, false);
        }
    }

    @Override
    public void setDeviceGroups(List<Integer> groups, GroupListener listener) {
        if (mSendDeviceId == Device.DEVICE_ID_UNKNOWN) {
            return;
        }
        mNewGroups.clear();
        mGroupAckListener = listener;
        boolean inProgress = false;
        for (int group : groups) {
            mNewGroups.add(group);
        }
        SingleDevice selectedDev = mDeviceStore.getSingleDevice(mSendDeviceId);

        // Send message to find out how many group ids the device supports for each model type.
        // Once a response is received to this command sendGroupAssign will be called to assign the groups.
        if (selectedDev.isModelSupported(LightModelApi.MODEL_NUMBER) && !selectedDev.isNumSupportedGroupsKnown(LightModelApi.MODEL_NUMBER)) {
            // Only query light model and assume power model supports the same number.
            mModelsToQueryForGroups.add(LightModelApi.MODEL_NUMBER);
            inProgress = true;
        }
        if (selectedDev.isModelSupported(SwitchModelApi.MODEL_NUMBER) && !selectedDev.isNumSupportedGroupsKnown(SwitchModelApi.MODEL_NUMBER)) {
            mModelsToQueryForGroups.add(SwitchModelApi.MODEL_NUMBER);
            inProgress = true;
        }
        if (selectedDev.isModelSupported(SensorModelApi.MODEL_NUMBER) && !selectedDev.isNumSupportedGroupsKnown(SensorModelApi.MODEL_NUMBER)) {
            mModelsToQueryForGroups.add(SensorModelApi.MODEL_NUMBER);
            inProgress = true;
        }
        if (selectedDev.isModelSupported(ActuatorModelApi.MODEL_NUMBER) && !selectedDev.isNumSupportedGroupsKnown(ActuatorModelApi.MODEL_NUMBER)) {
            mModelsToQueryForGroups.add(ActuatorModelApi.MODEL_NUMBER);
            inProgress = true;
        }
        if (selectedDev.isModelSupported(DataModelApi.MODEL_NUMBER) && !selectedDev.isNumSupportedGroupsKnown(DataModelApi.MODEL_NUMBER)) {
            mModelsToQueryForGroups.add(DataModelApi.MODEL_NUMBER);
            inProgress = true;
        }
        if (inProgress) {
            GroupModelApi.getNumModelGroupIds(mSendDeviceId, mModelsToQueryForGroups.peek());
        } else {
            // We already know the number of supported groups from a previous query, so go straight to assigning.
            assignGroups(selectedDev.getMinimumSupportedGroups());
            inProgress = true;
        }

        // There isn't any operation to do, so the dialog should be dismissed.
        if (!inProgress) {
            mGroupAckListener.groupsUpdated(mSendDeviceId, false, getString(R.string.group_query_fail));
        }
    }

    @Override
    public void removeDevice(SingleDevice device, RemovedListener listener) {
        mRemovedUuidHash = device.getUuidHash();
        mRemovedDeviceId = device.getDeviceId();
        mRemovedListener = listener;
        // Enable discovery so that the device uuid message is received when the device is unassociated.
        mService.setDeviceDiscoveryFilterEnabled(true);
        // Send CONFIG_RESET
        ConfigModelApi.resetDevice(device.getDeviceId());
        // Start a timer so that we don't wait for the ack forever.
        mMeshHandler.postDelayed(removeDeviceTimeout, REMOVE_ACK_WAIT_TIME_MS);
    }

    @Override
    public void removeDeviceLocally(int deviceId, RemovedListener listener) {
        mDeviceStore.removeDevice(deviceId);
        mRemovedListener.onDeviceRemoved(deviceId, true);
        mRemovedListener = null;
    }

    private void assignGroups(int numSupportedGroups) {
        if (mSendDeviceId == Device.DEVICE_ID_UNKNOWN)
            return;
        // Check the number of supported groups matches the number requested to be set.
        if (numSupportedGroups >= mNewGroups.size()) {

            mGroupAcksWaiting = 0;

            // Make a copy of existing groups for this device.
            mGroupsToSend = mDeviceStore.getSingleDevice(mSendDeviceId).getGroupMembershipValues();
            // Loop through existing groups.
            for (int i = 0; i < mGroupsToSend.size(); i++) {
                int groupId = mGroupsToSend.get(i);
                if (groupId != 0) {
                    int foundIndex = mNewGroups.indexOf(groupId);
                    if (foundIndex > -1) {
                        // The device is already a member of this group so remove it from the list of groups to add.
                        mNewGroups.remove(foundIndex);
                    } else {
                        // The device should no longer be a member of this group, so set that index to -1 to flag
                        // that a message must be sent to update this index.
                        mGroupsToSend.set(i, -1);
                    }
                }
            }
            // Now loop through currentGroups, and for every index set to -1 or zero send a group update command for
            // that index with one of our new groups if one is available. If there are no new groups to set, then just
            // send a message for all indices set to -1, to set them to zero.
            boolean commandSent = false;
            for (int i = 0; i < mGroupsToSend.size(); i++) {
                int groupId = mGroupsToSend.get(i);
                if (groupId == -1 || groupId == 0) {
                    if (mNewGroups.size() > 0) {
                        int newGroup = mNewGroups.get(0);
                        mNewGroups.remove(0);
                        commandSent = true;
                        sendGroupCommands(mSendDeviceId, i, newGroup);
                    } else if (groupId == -1) {
                        commandSent = true;
                        sendGroupCommands(mSendDeviceId, i, 0);
                    }
                }
            }
            if (!commandSent) {
                // There were no changes to the groups so no updates were sent. Just tell the listener
                // that the operation is complete.
                if (mGroupAckListener != null) {
                    mGroupAckListener.groupsUpdated(mSendDeviceId, true, getString(R.string.group_no_changes));
                }
            }
        } else {
            // Not enough groups supported on device.
            if (mGroupAckListener != null) {
                mGroupAckListener.groupsUpdated(mSendDeviceId, false,
                        getString(R.string.group_max_fail) + " " + numSupportedGroups + " " + getString(R.string.groups));
            }
        }
    }


    private void sendGroupCommands(int deviceId, int index, int group) {
        mGroupSuccess = true;
        SingleDevice dev = mDeviceStore.getSingleDevice(deviceId);

        if (dev.isModelSupported(LightModelApi.MODEL_NUMBER) && dev.getNumSupportedGroups(LightModelApi.MODEL_NUMBER) != 0) {
            mGroupAcksWaiting++;
            GroupModelApi.setModelGroupId(deviceId, LightModelApi.MODEL_NUMBER, index, 0, group);
            // If a light also supports power then set groups for that too.
            if (dev.isModelSupported(LightModelApi.MODEL_NUMBER) && dev.getNumSupportedGroups(LightModelApi.MODEL_NUMBER) != 0) {
                mGroupAcksWaiting++;
                GroupModelApi.setModelGroupId(deviceId, PowerModelApi.MODEL_NUMBER, index, 0, group);
            }
        } else if (dev.isModelSupported(SwitchModelApi.MODEL_NUMBER) && dev.getNumSupportedGroups(SwitchModelApi.MODEL_NUMBER) != 0) {
            mGroupAcksWaiting++;
            GroupModelApi.setModelGroupId(deviceId, SwitchModelApi.MODEL_NUMBER, index, 0, group);
        } else if (dev.isModelSupported(SensorModelApi.MODEL_NUMBER) && dev.getNumSupportedGroups(SensorModelApi.MODEL_NUMBER) != 0) {
            mGroupAcksWaiting++;
            GroupModelApi.setModelGroupId(deviceId, SensorModelApi.MODEL_NUMBER, index, 0, group);
        } else if (dev.isModelSupported(ActuatorModelApi.MODEL_NUMBER) && dev.getNumSupportedGroups(ActuatorModelApi.MODEL_NUMBER) != 0) {
            mGroupAcksWaiting++;
            GroupModelApi.setModelGroupId(deviceId, ActuatorModelApi.MODEL_NUMBER, index, 0, group);
        }

        // Check if device supports data model and that it supports groups. If it does, then setModelGroupId
        if (dev.isModelSupported(DataModelApi.MODEL_NUMBER) &&
                dev.getNumSupportedGroups(DataModelApi.MODEL_NUMBER) != 0) {
            mGroupAcksWaiting++;
            GroupModelApi.setModelGroupId(deviceId, DataModelApi.MODEL_NUMBER, index, 0, group);
        }

    }

    private static class MeshHandler extends Handler {
        private final WeakReference<MainActivity> mActivity;

        public MeshHandler(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        public void handleMessage(Message msg) {
            MainActivity parentActivity = mActivity.get();
            switch (msg.what) {
                case MeshService.MESSAGE_LE_CONNECTED: {
                    String address = msg.getData().getString(MeshService.EXTRA_DEVICE_ADDRESS);
                    parentActivity.mConnectedDevices.add(address);
                    if (!parentActivity.mConnected) {
                        parentActivity.onConnected(parentActivity.address_name.get(address));
                    }
                    break;
                }
                case MeshService.MESSAGE_LE_DISCONNECTED: {
                    int numConnections = msg.getData().getInt(MeshService.EXTRA_NUM_CONNECTIONS);
                    String address = msg.getData().getString(MeshService.EXTRA_DEVICE_ADDRESS);
                    if (address != null) {
                        String toRemove = null;
                        for (String s : parentActivity.mConnectedDevices) {
                            if (s.compareTo(address) == 0) {
                                toRemove = s;
                                break;
                            }
                        }
                        if (toRemove != null) {
                            parentActivity.mConnectedDevices.remove(toRemove);
                        }
                    }
                    if (numConnections == 0) {
                        parentActivity.mConnected = false;
                        if (!TextUtils.isEmpty(parentActivity.address_name.get(address)) && !TextUtils.equals("null", parentActivity.address_name.get(address))) {
                            parentActivity.onDisConnected(parentActivity.address_name.get(address));
                        }
                    }
                    break;
                }
                case MeshService.MESSAGE_LE_DISCONNECT_COMPLETE:
                    parentActivity.finish();
                    break;
                case MeshService.MESSAGE_REQUEST_BT:
                    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                    parentActivity.startActivityForResult(enableBtIntent, REQUEST_BT_RESULT_CODE);
                    break;
                case MeshService.MESSAGE_TIMEOUT: {
                    int expectedMsg = msg.getData().getInt(MeshService.EXTRA_EXPECTED_MESSAGE);
                    int id;
                    int meshRequestId;
                    if (msg.getData().containsKey(MeshService.EXTRA_UUIDHASH_31)) {
                        id = msg.getData().getInt(MeshService.EXTRA_UUIDHASH_31);
                    } else {
                        id = msg.getData().getInt(MeshService.EXTRA_DEVICE_ID);
                    }
                    meshRequestId = msg.getData().getInt(MeshService.EXTRA_MESH_REQUEST_ID);
                    parentActivity.onMessageTimeout(expectedMsg, id, meshRequestId);
                    break;
                }

                case MeshService.MESSAGE_ASSOCIATING_DEVICE:
                    int progress = msg.getData().getInt(MeshService.EXTRA_PROGRESS_INFORMATION);
                    if (parentActivity.mAssListener != null) {
                        parentActivity.mAssListener.associationProgress(progress, null);
                    }
                    break;
                case MeshService.MESSAGE_DEVICE_DISCOVERED: {
                    ParcelUuid uuid = msg.getData().getParcelable(MeshService.EXTRA_UUID);
                    int uuidHash = msg.getData().getInt(MeshService.EXTRA_UUIDHASH_31);
                    int rssi = msg.getData().getInt(MeshService.EXTRA_RSSI);
                    int ttl = msg.getData().getInt(MeshService.EXTRA_TTL);
                    if (parentActivity.mRemovedListener != null && parentActivity.mRemovedUuidHash == uuidHash) {
                        // This was received after a device was removed, so let the removed listener know.
                        parentActivity.mDeviceStore.removeDevice(parentActivity.mRemovedDeviceId);
                        parentActivity.mRemovedListener.onDeviceRemoved(parentActivity.mRemovedDeviceId, true);
                        parentActivity.mRemovedListener = null;
                        parentActivity.mRemovedUuidHash = 0;
                        parentActivity.mRemovedDeviceId = 0;
                        parentActivity.mService.setDeviceDiscoveryFilterEnabled(false);
                        removeCallbacks(parentActivity.removeDeviceTimeout);
                    } else if (parentActivity.mAssListener != null) {
                        // This was received after discover was enabled so let the UUID listener know.
                        parentActivity.mAssListener.newUuid(uuid.getUuid(), uuidHash, rssi, ttl);
                    }
                    break;
                }
                case MeshService.MESSAGE_DEVICE_APPEARANCE: {
                    // This is the appearance received when a device is in association mode.
                    // If appearance has been explicitly requested via CONFIG_DEVICE_INFO, then the appearance
                    // will be received in a MESSAGE_CONFIG_DEVICE_INFO.
                    byte[] appearance = msg.getData().getByteArray(MeshService.EXTRA_APPEARANCE);
                    String shortName = msg.getData().getString(MeshService.EXTRA_SHORTNAME);
                    int uuidHash = msg.getData().getInt(MeshService.EXTRA_UUIDHASH_31);
                    if (parentActivity.mAssListener != null) {
                        // This was received after discover was enabled so let the UUID listener know.
                        parentActivity.mAssListener.newAppearance(uuidHash, appearance, shortName);
                    }
                    break;
                }
                case MeshService.MESSAGE_DEVICE_ASSOCIATED: {
                    // New device has been associated and is telling us its device id.
                    // Request supported models before adding to DeviceStore, and the UI.
                    int deviceId = msg.getData().getInt(MeshService.EXTRA_DEVICE_ID);
                    int uuidHash = msg.getData().getInt(MeshService.EXTRA_UUIDHASH_31);
//                    Log.d("aa", "New device associated with id " + String.format("0x%x", deviceId));
                    if (parentActivity.mDeviceStore.getDevice(deviceId) == null) {
                        // Save the device id with the UUID hash so that we can store the UUID hash in the device
                        // object when MESSAGE_CONFIG_MODELS is received.
                        parentActivity.mDeviceIdtoUuidHash.put(deviceId, uuidHash);
                        // We add the device with no supported models. We will update that once we get the info.
                        if (uuidHash != 0) {
                            parentActivity.addDevice(deviceId, parentActivity.getDeviceType(), uuidHash, 0);
                        }
                        // If we don't already know about this device request its model support.
                        // We only need the lower 64-bits, so just request those.
                        ConfigModelApi.getInfo(deviceId, ConfigModelApi.DeviceInfo.MODEL_LOW);
                    }
                    break;
                }
                case MeshService.MESSAGE_CONFIG_DEVICE_INFO: {
                    int deviceId = msg.getData().getInt(MeshService.EXTRA_DEVICE_ID);
                    int uuidHash = parentActivity.mDeviceIdtoUuidHash.get(deviceId);
                    ConfigModelApi.DeviceInfo infoType =
                            ConfigModelApi.DeviceInfo.values()[msg.getData().getByte(MeshService.EXTRA_DEVICE_INFO_TYPE)];
                    if (infoType == ConfigModelApi.DeviceInfo.MODEL_LOW) {
                        long bitmap = msg.getData().getLong(MeshService.EXTRA_DEVICE_INFORMATION);
                        // If the uuidHash was saved for this device id then this is an expected message, so process it.
                        if (uuidHash != 0) {
                            // Remove the uuidhash from the array as we have received its model support now.
                            parentActivity.mDeviceIdtoUuidHash
                                    .removeAt(parentActivity.mDeviceIdtoUuidHash.indexOfKey(deviceId));
                            parentActivity.addDevice(deviceId, parentActivity.getDeviceType(), uuidHash, bitmap);
                            parentActivity.deviceAssociated(deviceId, true, null);
                        } else if (parentActivity.mDeviceIdtoUuidHash.size() == 0) {
//                            if (parentActivity.mInfoListener != null) {
//                                SingleDevice device = parentActivity.mDeviceStore.getSingleDevice(deviceId);
//                                if (device != null) {
//                                    device.setModelSupport(bitmap, 0);
//                                    parentActivity.mDeviceStore.addDevice(device);
//                                    parentActivity.mInfoListener.onDeviceConfigReceived(true);
//                                } else {
//                                    parentActivity.mInfoListener.onDeviceConfigReceived(false);
//                                }
//                            }
                        }
                    }
//                    } else if (infoType == ConfigModelApi.DeviceInfo.VID_PID_VERSION) {
//                        parentActivity.vid = msg.getData().getByteArray(MeshService.EXTRA_VID_INFORMATION);
//                        parentActivity.pid = msg.getData().getByteArray(MeshService.EXTRA_PID_INFORMATION);
//                        parentActivity.version = msg.getData().getByteArray(MeshService.EXTRA_VERSION_INFORMATION);
//                        if (parentActivity.mDeviceStore.getSingleDevice(deviceId).isModelSupported(BatteryModelApi.MODEL_NUMBER)) {
//                            parentActivity.getBatteryState(parentActivity.mInfoListener);
//                        } else if (parentActivity.mInfoListener != null) {
//                            parentActivity.mInfoListener.onDeviceInfoReceived(parentActivity.vid, parentActivity.pid, parentActivity.version, GroupAssignFragment.UNKNOWN_BATTERY_LEVEL,GroupAssignFragment.UNKNOWN_BATTERY_STATE, deviceId, true);
//                        } else {
//                            // shouldn't happen. Just in case for avoiding endless loops.
//                            parentActivity.hideProgress();
//                        }
//
//                    }
                    break;
                }
                case MeshService.MESSAGE_RECEIVE_BLOCK_DATA:
                    int _deviceId = msg.getData().getInt(MeshService.EXTRA_DEVICE_ID);
                    byte[] data = msg.getData().getByteArray(MeshService.EXTRA_DATA);
                    String batteryInfo = HexUtil.encodeHexStr(data);
                    if (batteryInfo.startsWith("b6")&& parentActivity.mBatteryListener != null) {
                        Log.d("aa", batteryInfo + "---------");
                        if (_deviceId != DEVICE_ADDR_BASE&&_deviceId != DEVICE_ID_UNKNOWN ) {
                            String batteryLevel = batteryInfo.substring(batteryInfo.length() - 2, batteryInfo.length());
                            Log.d("aa", _deviceId + "----" + batteryLevel+"---"+(_deviceId != DEVICE_ADDR_BASE&&_deviceId != DEVICE_ID_UNKNOWN));
                            double level = HexUtil.toDigit(batteryLevel.charAt(0), 1) * 16 + HexUtil.toDigit(batteryLevel.charAt(1), 1);
                            parentActivity.mBatteryListener.onBatteryLevelReceived(_deviceId, level);
                        }
                    }
                    break;

                case MeshService.MESSAGE_GROUP_NUM_GROUPIDS: {
//                    Log.d("aa", "MESSAGE_GROUP_NUM_GROUPIDS");
                    if (parentActivity.mGroupAckListener != null) {
                        int numIds = msg.getData().getByte(MeshService.EXTRA_NUM_GROUP_IDS);
                        int modelNo = msg.getData().getByte(MeshService.EXTRA_MODEL_NO);
                        int expectedModelNo = parentActivity.mModelsToQueryForGroups.peek();
                        int deviceId = msg.getData().getInt(MeshService.EXTRA_DEVICE_ID);
//                        Log.d("aa", "MESSAGE_GROUP_NUM_GROUPIDS:" + numIds + "--" + modelNo + "---" + expectedModelNo + "---" + deviceId);
                        if (expectedModelNo == modelNo) {
                            SingleDevice currentDev = parentActivity.mDeviceStore.getSingleDevice(deviceId);
                            if (currentDev != null) {
                                currentDev.setNumSupportedGroups(numIds, modelNo);
                                parentActivity.mDeviceStore.addDevice(currentDev);
                                // We know how many groups are supported for this model now so remove it from the queue.
                                parentActivity.mModelsToQueryForGroups.remove();
                                if (parentActivity.mModelsToQueryForGroups.isEmpty()) {
                                    // If there are no more models to query then we can assign groups now.
                                    parentActivity.assignGroups(currentDev.getMinimumSupportedGroups());
                                } else {
                                    // Otherwise ask how many groups the next model supports, by taking the next model number from the queue.
                                    GroupModelApi.getNumModelGroupIds(parentActivity.mSendDeviceId, parentActivity.mModelsToQueryForGroups.peek());
                                }
                            } else {
                                parentActivity.mGroupAckListener.groupsUpdated(parentActivity.mSendDeviceId, false, parentActivity.getString(R.string.group_query_fail));
                            }
                        }
                    }
                    break;
                }
                case MeshService.MESSAGE_GROUP_MODEL_GROUPID: {
//                    Log.d("aa", "MESSAGE_GROUP_MODEL_GROUPID:");
                    // This is the ACK returned after calling setModelGroupId.
                    if (parentActivity.mGroupAckListener != null && parentActivity.mGroupAcksWaiting > 0) {
                        parentActivity.mGroupAcksWaiting--;
                        int index = msg.getData().getByte(MeshService.EXTRA_GROUP_INDEX);
                        int groupId = msg.getData().getInt(MeshService.EXTRA_GROUP_ID);
//                        Log.d("aa", "MESSAGE_GROUP_MODEL_GROUPID:" + index + "--" + groupId);
                        // Update the group membership of this device in the device store.
                        SingleDevice updatedDev = parentActivity.mDeviceStore.getSingleDevice(parentActivity.mSendDeviceId);
                        try {
                            updatedDev.setGroupId(index, groupId);
                        } catch (IndexOutOfBoundsException exception) {
                            parentActivity.mGroupSuccess = false;
                        }
                        parentActivity.mDeviceStore.addDevice(updatedDev);


                        if (parentActivity.mGroupAcksWaiting == 0) {
                            // Tell the listener that the update was OK.
                            parentActivity.mGroupAckListener.groupsUpdated(
                                    parentActivity.mSendDeviceId, true,
                                    parentActivity.mGroupSuccess ? parentActivity.getString(R.string.group_update_ok) : parentActivity.getString(R.string.group_update_with_problems));
                        }
                    }
                    break;
                }
                case MeshService.MESSAGE_TRANSACTION_NOT_CANCELLED: {
                    Toast.makeText(parentActivity, "Association couldn't be cancelled.", Toast.LENGTH_SHORT).show();
                    break;
                }
                case MeshService.MESSAGE_TRANSACTION_CANCELLED: {
//                    parentActivity.deviceAssociated(false, parentActivity.getString(R.string.association_cancelled));
                    break;
                }
            }
        }
    }


    /**
     * Called when a response is not seen to a sent command.
     *
     * @param expectedMessage The message that would have been received in the Handler if there hadn't been a timeout.
     */
    private void onMessageTimeout(int expectedMessage, int id, int meshRequestId) {
        switch (expectedMessage) {
            case MeshService.MESSAGE_GROUP_MODEL_GROUPID:
//                if (mGroupAcksWaiting > 0) {
//                    if (mGroupAckListener != null) {
//                        // Timed out waiting for group update ACK.
//                        mGroupAckListener.groupsUpdated(mSendDeviceId, false,
//                                getString(R.string.group_timeout));
//                    }
//                    mGroupAcksWaiting = 0;
//                }
                break;
            case MeshService.MESSAGE_DEVICE_ASSOCIATED:
                // Fall through.
            case MeshService.MESSAGE_CONFIG_MODELS:
                // If we couldn't find out the model support for the device then we have to report association failed.
//                deviceAssociated(false, getString(R.string.association_failed));
//                if (mInfoListener!= null) {
//                    mInfoListener.onDeviceConfigReceived(false);
//                }
                break;
            case MeshService.MESSAGE_BATTERY_STATE:
//                if (mInfoListener!= null) {
//                    mInfoListener.onDeviceInfoReceived(vid,pid,version, GroupAssignFragment.UNKNOWN_BATTERY_LEVEL,GroupAssignFragment.UNKNOWN_BATTERY_STATE, mSendDeviceId, true);
//                }
                break;
            case MeshService.MESSAGE_GROUP_NUM_GROUPIDS:
//                if (mGroupAckListener != null) {
//                    mGroupAckListener.groupsUpdated(mSendDeviceId, false, getString(R.string.group_query_fail));
//                }
                break;
            case MeshService.MESSAGE_CONFIG_DEVICE_INFO:
                // if we were waiting to get the configModels once we associate the device, we just assume we couldn't get the models
                // that the device support, but the association was successful.
//                if (mDeviceIdtoUuidHash.size() > 0) {
//
//                    Device device =mDeviceStore.getDevice(mDeviceIdtoUuidHash.keyAt(0));
//                    mDeviceIdtoUuidHash.removeAt(0);
//                    if (device != null) {
//                        String name = device.getName();
//                        Toast.makeText(getApplicationContext(),
//                                name == null ? "Device" : name + " " + getString(R.string.added),
//                                Toast.LENGTH_SHORT).show();
//                    }
//                    deviceAssociated(true,null);
//                }
//                if (mInfoListener!= null) {
//                    mInfoListener.onDeviceConfigReceived(false);
//                }
//                if (mInfoListener != null) {
//                    mInfoListener.onDeviceInfoReceived(new byte[0],new byte[0],new byte[0],GroupAssignFragment.UNKNOWN_BATTERY_LEVEL,GroupAssignFragment.UNKNOWN_BATTERY_STATE, 0, false);
//                }
                break;
        }
    }

    private void deviceAssociated(int deviceId, boolean success, String message) {
        mAssociationTransactionId = -1;
        if (mAssListener != null) {
            mAssListener.deviceAssociated(deviceId, success, message);
        }
    }


    private void onConnected(final String name) {
        mConnected = true;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                AppMsg appMsg = AppMsg.makeText(MainActivity.this, '"' + name + '"' + " " + getString(R.string.state_connected), new AppMsg.Style(AppMsg.LENGTH_SHORT, R.color.red_pressed));
                appMsg.setLayoutGravity(Gravity.TOP);
                appMsg.show();
            }
        });
    }

    private void onDisConnected(String name) {
        new MaterialDialog.Builder(this).backgroundColorRes(R.color.white).content("'" + name + "'" + getResources().getString(R.string.disconnected)).canceledOnTouchOutside(false).positiveText(R.string.confirm).contentColorRes(R.color.gray_text4).show();
    }

    private void addDevice(int deviceId, int lightType, int uuidHash, long modelSupportBitmapLow) {
        String shortName = null;
        if (lightType == 1) {
            shortName = "Linkuphome C3";
        } else if (lightType == 2) {
            shortName = "Linkuphome R2";
        }
        SingleDevice device = new SingleDevice(deviceId, lightType, shortName, uuidHash, modelSupportBitmapLow, 0);
        mDeviceStore.addDevice(device);
    }


    private BluetoothAdapter.LeScanCallback mScanCallBack = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            if (!TextUtils.isEmpty(device.getName())) {
                address_name.put(device.getAddress(), device.getName());
            }
            mService.processMeshAdvert(device, scanRecord, rssi);
        }
    };

    private Runnable removeDeviceTimeout = new Runnable() {
        @Override
        public void run() {
            if (mRemovedListener != null) {
                // Timed out waiting for device UUID that indicates device removal happened.
                mRemovedListener.onDeviceRemoved(mRemovedDeviceId, false);
                mRemovedListener = null;
                mRemovedUuidHash = 0;
                mRemovedDeviceId = 0;
                mService.setDeviceDiscoveryFilterEnabled(false);
            }
        }
    };

    public void showFragment(BaseFragment fragment) {
        if (fragment != null) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.replace(R.id.container, fragment);
            ft.addToBackStack(null);
            ft.commitAllowingStateLoss();
        }
    }

    @Override
    public void onBackPressed() {
        if (!BackHandlerHelper.handleBackPress(this)) {
            super.onBackPressed();
        }
    }


}
